-- BraveVSDragonM
-- Created by chengb Jul/27/2016
-- 勇者斗恶龙副本模块

module("BraveVSDragonM", package.seeall);

-- 配置表
local braveDragonLockTable = {};
local lockAliasToIdMap = {};
local braveDragonMixedTable = {};

-- 龙王入伙请求选择类型
CHOOSE_TYPE_REFUSE         =  0   -- 拒绝
CHOOSE_TYPE_ACCEPT         =  1   -- 接受
CHOOSE_TYPE_REFUSE_AGAIN   =  2   -- 再次拒绝

-- 死亡延迟播放时间
DIE_DELAY_TIME = 0.7;

-- 内部函数声明
local costSubmitItem;
local doBuildingBonus;
local doConffinEvent;
local doElementTransform;
local setBuidingBonusTaken;
local setEnteredUnreal;
local setTalkedWithRole;
local setLittleBoyProgess;
local talkWithKing;
local talkWithPriest;
local talkWithPoet;
local talkWithChancellor;
local tryLightUpSwampCave;
local whenLeaveDungeon;
local whenLeaveUnreal;
local whenCaveLightUp;
local whenEquip;
local whenUpgradeEquip;
local whenBossDied;
local whenCombatRound;
local whenSaveProgress;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    braveDragonLockTable = {};
    braveDragonMixedTable = {};

    -- 转换勇者斗恶龙建筑锁配置
    local brave_dragon_lock_CSV = get_brave_dragon_lock_CSV();
    for i = 1, #brave_dragon_lock_CSV do
        local id = brave_dragon_lock_CSV[i].id;
        braveDragonLockTable[id] = brave_dragon_lock_CSV[i];

        -- 根据别名存放建筑锁配置
        local alias = brave_dragon_lock_CSV[i].alias;
        lockAliasToIdMap[alias] = id;
    end
    brave_dragon_lock_CSV = nil;

    -- 载入勇者斗恶龙杂项配置表
    local brave_dragon_mixed_CSV = get_brave_dragon_mixed_CSV();
    for i = 1, #brave_dragon_mixed_CSV do
        local key = brave_dragon_mixed_CSV[i].key;
        braveDragonMixedTable[key] = brave_dragon_mixed_CSV[i].value;
    end
    brave_dragon_mixed_CSV = nil;
end

-- 初始化
function init()
    if not _init then
        loadCsv();
    end

    EventMgr.removeAll("BraveVSDragonM");

    -- 关注玩家离开迷宫事件
    EventMgr.register("BraveVSDragonM", event.LEAVE_DUNGEON, whenLeaveDungeon);

    -- 关注玩家离开夹层迷宫的事件
    EventMgr.register("BraveVSDragonM", event.LEAVE_UNREAL_DUNGEON, whenLeaveUnreal);

    -- 关注洞窟点亮的事件
    EventMgr.register("BraveVSDragonM", event.TRY_LIGHT_CAVE, whenCaveLightUp);

    -- 关注穿装备的事件
    EventMgr.register("BraveVSDragonM", event.EQUIP, whenEquip);

    -- 关注装备升级的事件
    EventMgr.register("BraveVSDragonM", event.UPGRADE_EQUIP, whenUpgradeEquip);

    -- 关注BOSS死亡事件
    EventMgr.register("BraveVSDragonM", event.BOSS_DIE, whenBossDied);

    -- 关注开始新回合的事件
    CombatM.registerStartRoundCB("brave_vs_dragon", whenCombatRound);

    -- 关注服务器保存进度的结果
    EventMgr.register("BraveVSDragonM", event.SAVE_BRAVE_DRAGON, whenSaveProgress);
end

-- 检索勇者斗恶龙建筑锁配置
function queryLock(id, path)
    if not braveDragonLockTable[id] then
        return nil;
    end

    if not path then
        return braveDragonLockTable[id];
    end

    return braveDragonLockTable[id][path];
end

-- 根据别名查找勇者斗恶龙建筑锁ID
function queryLockIdByAlias(alias)
    return lockAliasToIdMap[alias];
end

-- 检索勇者斗恶龙杂项配置
function queryMixed(key)
    return braveDragonMixedTable[key];
end

-- 是否在勇者斗恶龙副本中
function isInBraveVSDragon()
    if not DungeonM.isInDungeon() then
        return false;
    end

    local dungeonId = DungeonM.getDungeonId();
    local dbase = DungeonAreaM.query(dungeonId, "dbase");
    if type(dbase) ~= 'table' then
        return false;
    end

    return dbase["brave_vs_dragon"] == 1;
end

-- 玩家离开迷宫的回调
function whenLeaveDungeon()
    -- 清除勇者斗恶龙数据
    if ME.user.dbase:query("brave_vs_dragon") ~= nil then
        ME.user.dbase:delete("brave_vs_dragon");
    end
end

-- 玩家离开夹层迷宫的回调
function whenLeaveUnreal(args)
    local unrealId = args.unrealId;
    local dbase = UnrealDungeonM.query(unrealId, "dbase");

    -- 如果是勇者斗恶龙的夹层迷宫，需要标记进入过
    if dbase["brave_vs_dragon"] == 1 then
        setEnteredUnreal(unrealId);

        -- 清除洞窟点亮标记
        if DungeonM.getDungeonMixedField("cave_light_up") == 1 then
            DungeonM.setDungeonMixedField("cave_light_up", 0);
        end
    end
end

-- 建筑是否解锁
function isBuidingUnlock(id)
    if queryLock(id, "unlock_condition") == "default" then
        return true;
    end

    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local unlockIds = info["unlock_ids"] or {};
    return unlockIds[id] == 1;
end

-- 解除建筑的封锁
function unlockBuilding(id)
    if not queryLock(id) then
        trace("BraveVSDragonM", "建筑(%d)不存在。", id);
        return false;
    end

    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local unlockIds = info["unlock_ids"] or {};
    unlockIds[id] = 1;

    info["unlock_ids"] = unlockIds;
    ME.user.dbase:set("brave_vs_dragon", info);

    trace("BraveVSDragonM", "成功解除建筑(%d)的封锁。", id);

    return true;
end

-- 根据条件解锁建筑
function unlockBuildingByCondition(condition)
    for id, row in ipairs(braveDragonLockTable) do
        if row.unlock_condition == condition then
            unlockBuilding(id);
        end
    end
end

-- 执行元素转换（这里不做判断）
function doElementTransform(grid, targetPos)
    local elementDbase = DungeonM.queryElement(grid.element, "dbase");
    local toElement = elementDbase["trans_element"];

    assert(type(toElement) == "number", "必须配置转换元素");

    local toClass = DungeonM.queryElement(toElement, "class");
    local para = { ["class"] = toClass, ["element"] = toElement };
    DungeonM.transformGrid(toClass, targetPos, GRID_STATE_OPEN, para);
end

-- 是否进入过某个夹层
function hasEnteredUnreal(unrealId)
    local info = ME.user.dbase:query("brave_vs_dragon");
    if type(info) ~= 'table' then
        return false;
    end

    local enteredUnreals = info["entered_unreals"];
    if type(enteredUnreals) ~= 'table' then
        return false;
    end

    return enteredUnreals[unrealId] == 1;
end

-- 标记进入过某个夹层
function setEnteredUnreal(unrealId)
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local enteredUnreals = info["entered_unreals"] or {};
    if enteredUnreals[unrealId] == 1 then
        -- 已经标记过了
        return;
    end

    enteredUnreals[unrealId] = 1;
    info["entered_unreals"] = enteredUnreals;
    ME.user.dbase:set("brave_vs_dragon", info);
end

-- 公主是否出现了
function hasPrincessAppeared()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["princess_appeared"] == 1;
end

-- 标记公主出现了
function setPrincessAppeared()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["princess_appeared"] = 1;
    ME.user.dbase:set("brave_vs_dragon", info);
end

-- 是否营救过公主
function hasRescuedPrincess()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["rescued_princess"] == 1;
end

-- 是否领取过指定格子类型的建筑奖励
function hasGotBuildingBonus(class)
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local buildingBonusRecord = info["building_bonus_record"] or {};
    return buildingBonusRecord[class] == 1;
end

-- 是否和角色对话过
function hasTalkedWithRole(gridType, progress)
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local talkedInfo = info["talked_with_role"] or {};
    local curProgess = talkedInfo[gridType] or 0;
    return curProgess >= progress;
end

-- 是否击败了龙王
function hasDefeatedDragon()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["defeated_dragon"] == 1;
end

-- 是否领取过击败龙王的奖励
function hasGotDefeatDragonBonus()
    return ME.user.dbase:query("got_defeat_dragon_bonus") == 1;
end

-- 是否复活过侍卫队长
function hasRevivedGuardsLeader()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["revived_guards_leader"] == 1;
end

-- 是否接受过商人任务
function hasReceivedBussiness()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["received_bussiness"] == 1;
end

-- 获取已领取的小徽章奖励次数
function getSmallBadgeBonusTimes()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["small_badge_bonus_times"] or 0;
end

-- 岩浆障碍是否消除
function hasRemovedMagaMaObstacle()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["removed_magma_obstacle"] == 1;
end

-- 获取小男孩事件的进度
function getLittleBoyProgress()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["little_boy_progress"] or 0;
end

-- 设置小男孩事件的进度
function setLittleBoyProgess(progress)
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["little_boy_progress"] = progress;
    ME.user.dbase:set("little_boy_progress", info);
    trace("BraveVSDragonM", "小男孩事件进度更新为：%d", progress);
end

-- 领取击败龙王的奖励
function takeDefeatDragonBonus(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_KING then
        trace("BraveVSDragonM", "格子(%d)不是国王。", pos);
        return false;
    end

    -- 是否拥有光之玉
    local classId = FormulaM.invoke("LIGHT_JADE_ITEM_ID");
    local amount = ItemM.getAmount(ME.user, classId);
    if amount <= 0 then
        trace("BraveVSDragonM", "没有光之玉。");
        return false;
    end

    -- 扣除光之玉
    ItemM.costAmount(ME.user, classId, amount);

    -- 执行奖励
    local bonus = FormulaM.invoke("CALC_DEFEAT_DARGON_BONUS");
    BonusM.doBonus(bonus, "defeat_dragon_bonus");

    -- 标记领取过奖励
    if ME.user.dbase:query("got_defeat_dragon_bonus") ~= 1 then
        ME.user.dbase:set("got_defeat_dragon_bonus", 1);
    end

    return true;
end

-- 标记建筑奖励已领取
function setBuidingBonusTaken(grid)
    local class = grid.class;
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local buildingBonusRecord = info["building_bonus_record"] or {};
    if buildingBonusRecord[class] ~= 1 then
        buildingBonusRecord[class] = 1;
        info["building_bonus_record"] = buildingBonusRecord;
        ME.user.dbase:set("brave_vs_dragon", info);
    end
end

-- 消耗提交道具（如果没有配置消耗，默认通过）
function costSubmitItem(grid)
    local gridType = grid.type;
    local cost;
    if gridType == GRID_TYPE_CLOUDS_SHRINE then
        -- 云雨祠堂
        cost = FormulaM.invoke("CALC_CLOUDS_SHRINE_COST");
    else
        local elementDbase = DungeonM.queryElement(grid.element, "dbase");
        if type(elementDbase["submit_item"]) == "number" then
            cost = { 1, elementDbase["submit_item"], 1 };
        end
    end

    if type(cost) == "table" and #cost > 0 then
        local classId = cost[2];
        local amount = cost[3];

        -- 如果是装备
        if EquipM.isEquipment(classId) then
            if not EquipM.costEquip(classId) then
                return false;
            end
        else
            local curAmount = ItemM.getAmount(ME.user, classId);
            if curAmount < amount then
                trace("BraveVSDragonM", "道具(%d)数据不足，需求：%d，当前：%d",
                    classId, amount, curAmount);
                return false;
            end

            ItemM.costAmount(ME.user, classId, amount);
        end
    end

    return true;
end

-- 执行建筑奖励
function doBuildingBonus(grid)
    local gridType = grid.type;
    local desc;
    local bonus;
    local remainGrid = true;  -- 领取奖励后是否保留格子
    local elementDbase = DungeonM.queryElement(grid.element, "dbase");

    if gridType == GRID_TYPE_KING then
        -- 国王
        bonus = FormulaM.invoke("CALC_KING_BONUS");
        desc = "king_bonus";
    elseif gridType == GRID_TYPE_PRINCESS then
        -- 公主
        bonus = FormulaM.invoke("CALC_PRINCESS_BONUS");
        desc = "princess_bonus";
    elseif gridType == GRID_TYPE_CHANCELLOR then
        -- 大臣
        bonus = FormulaM.invoke("CALC_CHANCELLOR_BONUS");
        desc = "chancellor_bonus";
    elseif gridType == GRID_TYPE_BRAVE_CAVE then
        -- 勇者洞窟
        bonus = FormulaM.invoke("CALC_BRAVE_CAVE_BONUS");
        desc = "brave_cave_bonus";
    elseif gridType == GRID_TYPE_CLOUDS_SHRINE then
        -- 云雨祠堂
        bonus = FormulaM.invoke("CALC_CLOUDS_SHRINE_BONUS");
        desc = "clouds_shrine_bonus";
    elseif gridType == GRID_TYPE_BRAVE_ITEM then
        -- 物品
        bonus = grid.bonus;
        desc = "brave_item";
        remainGrid = false;
    elseif gridType == GRID_TYPE_BRAVE_SUBMITTER then
        -- 提交道具NPC
        bonus = grid.bonus;
        desc = "brave_submitter";
    elseif gridType == GRID_TYPE_DRUNK_BOX then
        -- 醉汉储物箱
        bonus = grid.bonus;
        desc = "drunk_box";
        remainGrid = false;
    elseif gridType == GRID_TYPE_PIXY_WELL_BRAVE then
        -- 妖精之井
        bonus = grid.bonus;
        desc = "pixy_well_brave";
    elseif gridType == GRID_TYPE_PIXY_SPRING then
        -- 妖精之泉
        bonus = grid.bonus;
        desc = "pixy_spring";
    elseif gridType == GRID_TYPE_COFFIN then
        -- 棺材
        doConffinEvent(grid);
        return;
    elseif gridType == GRID_TYPE_BUSSNESSMAN then
        -- 商人，梅尔基德的商人不消失
        bonus = grid.bonus;
        desc = "bussiness_bonus";
        if elementDbase["mel_kid"] then
            remainGrid = true;
        else
            remainGrid = false;
        end
    elseif gridType == GRID_TYPE_LAIJIA_GRAVE then
        -- 莱迦之墓
        bonus = grid.bonus;
        desc = "laijia_grave";
    elseif gridType == GRID_TYPE_BRAVE_CERTIFICATE then
        -- 勇者之证
        bonus = grid.bonus;
        desc = "brave_certificate";
        remainGrid = false;
    elseif gridType == GRID_TYPE_GRAVE then
        -- 坟墓
        bonus = grid.bonus;
        desc = "grave_bonus";
    elseif gridType == GRID_TYPE_HOLY_SHRINE then
        -- 神圣祠堂
        bonus = grid.bonus;
        desc = "holy_shrine";
        remainGrid = false;
    elseif gridType == GRID_TYPE_DRAGON_KING then
        -- 龙王
        bonus = grid.bonus;
        desc = "dragon_king";
        remainGrid = false;
    elseif gridType == GRID_TYPE_MONSTER_BUILDING then
        -- 骸骨堆
        bonus = grid.bonus;
        desc = "bones_heap";
    end

    if not desc then
        assert(false, "必须填写desc");
    end

    if type(bonus) ~= "table" or #bonus == 0 then
        trace("BraveVSDragonM", "格子类型(%d)没有建筑奖励。", gridType);
        return;
    end

    if grid.bonus then
        grid.bonus = nil;
    end

    -- 如果不保留格子，则令格子失效
    if not remainGrid then
        grid:changeState(GRID_STATE_DISABLE);
    end

    -- 执行奖励
    if type(bonus[1]) == 'number' then
        -- 只有一种奖励
        BonusM.doBonus(bonus, desc);
    else
        -- 有多种奖励
        for _, arr in pairs(bonus) do
            BonusM.doBonus(arr, desc);
        end
    end

    if type(elementDbase["unlock_by_condition"]) == 'string' then
        -- 解除建筑封锁
        unlockBuildingByCondition(elementDbase["unlock_by_condition"]);
    end

    EventMgr.fire(event.BRAVE_BUILDING_BONUS, { ["pos"] = grid:getPos(), ["bonus"] = bonus, ["remainGrid"] = remainGrid, });
end

-- 领取建筑奖励
function takeBuildingBonus(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if hasGotBuildingBonus(grid.class) then
        trace("BraveVSDragonM", "格子(%d)的建筑奖励已被领取。", pos);
        return false;
    end

    if not costSubmitItem(grid) then
        trace("BraveVSDragonM", "消耗提交道具失败。");
        return false;
    end

    -- 标记领取过奖励
    setBuidingBonusTaken(grid);

    -- 执行奖励
    doBuildingBonus(grid);

    return true;
end

-- 标记和角色对话过
function setTalkedWithRole(gridType, progress)
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["talked_with_role"] = info["talked_with_role"] or {};
    info["talked_with_role"][gridType] = progress;
    ME.user.dbase:set("brave_vs_dragon", info);
end

-- 和角色对话
function talkWithRole(pos, progress)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    local gridType = grid.type;
    if hasTalkedWithRole(gridType, progress) then
        trace("BraveVSDragonM", "格子(%d)已经进行过第(%d)次对话了。", pos, progress);
        return false;
    end

    local ret = false;
    if gridType == GRID_TYPE_KING then
        -- 国王
        ret = talkWithKing(progress);
    elseif gridType == GRID_TYPE_PRIEST then
        -- 神官
        ret = talkWithPriest();
    elseif gridType == GRID_TYPE_CHANCELLOR then
        -- 大臣
        ret = talkWithChancellor();
    elseif gridType == GRID_TYPE_POET then
        -- 吟游诗人
        ret = talkWithPoet();
    end

    if not ret then
        trace("BraveVSDragonM", "格子类型(%d)不能对话。", gridType);
        return false;
    end

    -- 标记和角色对话过
    setTalkedWithRole(gridType, progress);

    return true;
end

-- 和国王对话
function talkWithKing(progress)
    trace("BraveVSDragonM", "发起和国王的第(%d)次对话。", progress);

    -- 首次对话
    if progress == 1 then
        unlockBuildingByCondition("talk_with_king");
    end

    return true;
end

-- 和神官对话
function talkWithPriest()
    return true;
end

-- 和大臣对话
function talkWithChancellor()
    return true;
end

-- 和吟游诗人对话
function talkWithPoet()
    return true;
end

-- 提交小徽章
function submitSmallBadge(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_KING then
        trace("BraveVSDragonM", "格子(%d)不是国王。", pos);
        return false;
    end

    if allSmallBadgeBonusTaken() then
        trace("BraveVSDragonM", "提交次数已达上限。");
        return false;
    end

    local classId = FormulaM.invoke("SMALL_BAGDGE_ITEM_ID");
    if ItemM.getAmount(ME.user, classId) <= 0 then
        trace("BraveVSDragonM", "没有道具(%d)", classId);
        return false;
    end

    ItemM.costAmount(ME.user, classId, 1);

    -- 执行奖励
    local bonus = FormulaM.invoke("CALC_SMALL_BADGE_BONUS");
    BonusM.doBonus(bonus, "small_badge_bonus");

    -- 记录奖励次数
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local cur_times = info["small_badge_bonus_times"] or 0;
    info["small_badge_bonus_times"] = cur_times + 1;
    ME.user.dbase:set("brave_vs_dragon", info);

    EventMgr.fire(event.SUBMIT_SMALL_BADGE, { ["pos"] = pos, ["bonus"] = bonus, });

    return true;
end

-- 公主出现
function princessAppear(pos)
    if hasPrincessAppeared() then
        trace("BraveVSDragonM", "公主已经出现过了。");
        return false;
    end

    -- 标记公主出现过
    setPrincessAppeared();

    -- 解除相关建筑封锁
    unlockBuildingByCondition("princess_appear");

    -- 查找公主的格子位置
    local princessPos;
    for p = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(p);
        if grid.type == GRID_TYPE_PRINCESS then
            princessPos = p;
            break;
        end
    end

    assert(type(princessPos) == "number", "必须配置公主格子");

    EventMgr.fire(event.PRINCESS_APPEARED, { ["princessPos"] = princessPos, });

    return true;
end

-- 是否拥有光之玉
function ownedLightJade()
    local classId = FormulaM.invoke("LIGHT_JADE_ITEM_ID");
    return ItemM.getAmount(ME.user, classId) > 0;
end

-- 是否领取了所有的小徽章奖励
function allSmallBadgeBonusTaken()
    local maxTimes = FormulaM.invoke("MAX_SMALL_BADGE_BONUS_TIMES");
    return getSmallBadgeBonusTimes() >= maxTimes;
end

-- 醉汉消失
function drunkDiappear(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_DRUNK then
        trace("BraveVSDragonM", "格子(%d)不是醉汉。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    local dbase = DungeonM.queryElement(grid.element, "dbase");
    if type(dbase["submit_item"]) == "number" then
        if ItemM.getAmount(ME.user, dbase["submit_item"]) <= 0 then
            trace("BraveVSDragonM", "道具(%d)数量不足。", dbase["submit_item"]);
            return false;
        else
            ItemM.costAmount(ME.user, dbase["submit_item"], 1);
        end
    elseif type(dbase["submit_item"]) == 'table' then
        -- 指定的是一个列表，则只要拥有其中一种道具即可
        local classId;
        for _, id in ipairs(dbase["submit_item"]) do
            if DungeonServiceM.getDungeonItemAmount(id) > 0 then
                classId = id;
                break;
            end
        end

        if type(classId) ~= 'number' then
            trace("BraveVSDragonM", "道具数量不足。");
            return false;
        end

        DungeonServiceM.costDungeonItem(classId, 1);
    end

    -- 格子失效
    grid:changeState(GRID_STATE_DISABLE);

    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["drunk_disappear"] = 1;
    ME.user.dbase:set("brave_vs_dragon", info);

    return true;
end

-- 醉汉是否消失了
function isDrunkDisapear()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["drunk_disappear"] == 1;
end

-- 开启醉汉的储物箱子
function openDrunkBox(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_DRUNK_BOX then
        trace("BraveVSDragonM", "格子(%d)不是醉汉储物箱。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    if hasGotBuildingBonus(grid.class) then
        trace("BraveVSDragonM", "格子(%d)的建筑奖励已被领取。", pos);
        return false;
    end

    if not isDrunkDisapear() then
        trace("BraveVSDragonM", "醉汉挡住了去路。");
        return false;
    end

    -- 标记领取过奖励
    setBuidingBonusTaken(grid);

    doBuildingBonus(grid);

    return true;
end

-- 妖精之泉事件
function doPixySpringEvent(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_PIXY_SPRING then
        trace("BraveVSDragonM", "格子(%d)不是妖精之泉。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    local dbase = DungeonM.queryElement(grid.element, "dbase");
    local classId = dbase["submit_item"];
    if type(classId) == "number" then
        if ItemM.getAmount(ME.user, classId) <= 0 then
            trace("BraveVSDragonM", "道具(%d)数量不足。", classId);
            return false;
        end

        ItemM.costAmount(ME.user, classId, 1);
    end

    -- 标记已领取过奖励
    setBuidingBonusTaken(grid);

    -- 转换格子
    local targetPos = dbase["trans_pos"];

    assert(type(targetPos) == 'number', "必须配置转换的格子位置");

    doElementTransform(grid, targetPos);

    return true;
end

-- 执行旅者帐篷事件
function doTravellerTentEvent(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_TRAVELLER_TENT then
        trace("BraveVSDragonM", "格子(%d)不是旅者帐篷。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    -- 计算消耗
    local cost = FormulaM.invoke("CALC_TRAVELLER_TENT_COST");
    if cost[1] == 1 then
        local classId = cost[2];
        local amount  = cost[3];
        local curAmount = ItemM.getAmount(ME.user, classId);
        if curAmount < amount then
            trace("BraveVSDragonM", "道具(%d)数量不足，需求：%d，当前：%d", classId, amount, curAmount);
            return false;
        end

        -- 扣除物品
        ItemM.costAmount(ME.user, classId, amount);

    elseif cost[2] == 2 then
        local attrib = cost[2];
        local amount = cost[3];
        local curAmount = ME.user:queryAttrib(attrib);
        if curAmount < amount then
            trace("BraveVSDragonM", "属性(%s)数量不足，需求：%d，当前：%d", attrib, amount, curAmount);
            return false;
        end

        -- 扣除属性
        ME.user:costAttrib(attrib, amount);
    else
        assert(false, "消耗配置错误");
    end

    -- 恢复全部生命值及魔法值
    local hpAdd = ME.user.dbase:query("max_hp", 0) - ME.user.dbase:query("hp", 0);
    ME.user:addAttrib("hp", hpAdd, "traveller_tent");

    local mpAdd = ME.user.dbase:query("max_mp", 0) - ME.user.dbase:query("mp", 0);
    ME.user:addAttrib("mp", mpAdd, "traveller_tent");

    trace("BraveVSDragonM", "恢复hp：%d点，mp：%d点", hpAdd, mpAdd);

    EventMgr.fire(event.TRAVELLER_TENT, { ["hp"] = hpAdd, ["mp"] = mpAdd, ["pos"] = pos, });

    return true;
end

-- 获取侍卫队长奖励次数
function getGuardsLeaderBonusTimes()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["guards_leader_bonus_times"] or 0;
end

-- 领取侍卫队长奖励
function takeGuardsLeaderBonus(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_GUARDS_LEADER then
        trace("BraveVSDragonM", "格子(%d)不是侍卫队长。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    -- 计算奖励
    local bonus = FormulaM.invoke("CALC_GAURDS_LEADER_BONUS");
    if #bonus == 0 then
        trace("BraveVSDragonM", "格子(%d)已经没有可领取的奖励。\n", pos);
        return false;
    end

    -- 记录奖励次数
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["guards_leader_bonus_times"] = (info["guards_leader_bonus_times"] or 0) + 1;
    ME.user.dbase:set("brave_vs_dragon", info);

    -- 如果是首次奖励，格子要失效
    local isDisappear = false;
    if info["guards_leader_bonus_times"] == 1 then
        grid:changeState(GRID_STATE_DISABLE);
        isDisappear = true;
    end

    for _, arr in ipairs(bonus) do
        BonusM.doBonus(arr, "guards_leader_bonus");
    end

    -- 解除相关建筑封锁
    unlockBuildingByCondition("guards_leader_bonus");

    EventMgr.fire(event.GRUARDS_LEADER_BONUS, { ["pos"] = pos, ["bonus"] = bonus, ["isDisappear"] = isDisappear, });

    return true;
end

-- 解救公主
function rescuePrincess(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_PRINCESS then
        trace("BraveVSDragonM", "格子(%d)不是侍卫队长。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    if not DungeonM.isAllMonsterDead() then
        trace("BraveVSDragonM", "还有怪物没死亡。", pos);
        return false;
    end

    if hasRescuedPrincess() then
        trace("BraveVSDragonM", "公主已经被解救过了。");
        return false;
    end

    -- 格子失效
    grid:changeState(GRID_STATE_DISABLE);

    -- 标记公主已被解救
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["rescued_princess"] = 1;
    ME.user.dbase:set("brave_vs_dragon", info);

    EventMgr.fire(event.RESCUE_PRINCESS, { ["pos"] = pos, });

    return true;
end

-- 能否点亮沼泽洞窟
function canLightSwampCave()
    local props = PropM.fetchProps(ME.user, "show_hide_item");
    return #props > 0;
end

-- 是否在沼泽洞窟中
function isInSwampCave()
    if not UnrealDungeonM.isInUnreal() then
        return false;
    end

    local unrealId = UnrealDungeonM.getUnrealId();
    local dbase = UnrealDungeonM.query(unrealId, "dbase");
    return  dbase["swamp_cave"] == 1;
end

-- 是否在石巨像BOSS战中
function isInStoneStatue()
    if not UnrealDungeonM.isInUnreal() then
        return false;
    end

    local unrealId = UnrealDungeonM.getUnrealId();
    local dbase = UnrealDungeonM.query(unrealId, "dbase");
    return  dbase["stone_statue"] == 1;
end

-- 是否在龙王BOSS战中
function isInDragonKing()
    if not UnrealDungeonM.isInUnreal() then
        return false;
    end

    local unrealId = UnrealDungeonM.getUnrealId();
    local dbase = UnrealDungeonM.query(unrealId, "dbase");
    return  dbase["dragon_king"] == 1;
end

-- 洞窟点亮的回调
function whenCaveLightUp()
    if not isInSwampCave() then
        -- 不在沼泽洞窟中
        return;
    end

    -- 尝试点亮沼泽洞窟
    tryLightUpSwampCave();
end

-- 穿戴装备的回调
function whenEquip(classId)
    local equipType = EquipM.query(classId, "type");
    if equipType ~= EQUIP_TYPE_RISKER then
        -- 不是冒险系装备
        return;
    end

    if not isInSwampCave() then
        -- 不在沼泽洞窟中
        return;
    end

    -- 尝试点亮沼泽洞窟
    tryLightUpSwampCave();
end

-- 装备升级的回调
function whenUpgradeEquip(args)
    local classId = args.nextClassId;
    local equipType = EquipM.query(classId, "type");
    if equipType ~= EQUIP_TYPE_RISKER then
        -- 不是冒险系装备
        return;
    end

    if not isInSwampCave() then
        -- 不在沼泽洞窟中
        return;
    end

    -- 尝试点亮沼泽洞窟
    tryLightUpSwampCave();
end

-- 尝试点亮沼泽洞窟
function tryLightUpSwampCave()
    if DungeonM.getDungeonMixedField("cave_light_up") == 1 then
        -- 已经处于点亮状态
        return;
    end

    if not canLightSwampCave() then
        -- 不满足点亮条件
        return;
    end

    -- 标记洞穴处于点亮状态
    DungeonM.setDungeonMixedField("cave_light_up", 1);

    local id = queryLockIdByAlias("swamp_cave");
    local dbase = queryLock(id, "dbase");
    if not dbase["light_up_trans"] then
        return;
    end

    -- 转换格子
    for serverPos, toElement in pairs(dbase["light_up_trans"]) do
        local targetPos = serverPos + 1;
        local toClass = DungeonM.queryElement(toElement, "class");
        local para = { ["class"] = toClass, ["element"] = toElement };
        if toClass == GRID_TYPE_MONSTER then
            DungeonM.transGridToMonster(targetPos, toElement);
        else
            DungeonM.transformGrid(toClass, targetPos, GRID_STATE_OPEN, para);
        end
    end

    -- 抛出洞窟被点亮的事件
    EventMgr.fire(event.CAVE_LIGHT_UP);
end

-- 执行棺材的建筑事件
function doConffinEvent(grid)
    doElementTransform(grid, grid:getPos());

    -- 标记侍卫队长已被复活
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["revived_guards_leader"] = 1;
    ME.user.dbase:set("brave_vs_dragon", info);
end

-- BOSS死亡的事件
function whenBossDied(args)
    if isInStoneStatue() then
        -- 如果在石巨像BOSS战中，解除相应的建筑封锁
        unlockBuildingByCondition("kill_stone_statue");
    elseif isInDragonKing() then
        -- 如果在龙王BOSS战中，标记击败了龙王
        local info = ME.user.dbase:query("brave_vs_dragon") or {};
        info["defeated_dragon"] = 1;
        ME.user.dbase:set("brave_vs_dragon", info);
    end
end

-- 接收商人任务
function receiveBussiness(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_BUSSNESSMAN then
        trace("BraveVSDragonM", "格子(%d)不是商人。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    if hasReceivedBussiness() then
        trace("BraveVSDragonM", "已经接受过商人任务了。");
        return false;
    end

    -- 标记接受了商人任务
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["received_bussiness"] = 1;
    ME.user.dbase:set("brave_vs_dragon", info);

    -- 计算奖励
    local bonus = FormulaM.invoke("CALC_RECEIVE_BUSSINESS_BONUS");
    BonusM.doBonus(bonus, "receive_bussiness");

    -- 解除相关建筑封锁
    unlockBuildingByCondition("recieve_bussiness");

    return true;
end

-- 执行小男孩的建筑事件
function doLittleBoyEvent(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_LITTLE_BOY then
        trace("BraveVSDragonM", "格子(%d)不是小男孩。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    local dbase = DungeonM.queryElement(grid.element, "dbase");

    -- 获取小男孩事件的进度
    local progress = getLittleBoyProgress();
    if progress == 0 then
        -- 首次触发事件
        if type(dbase["submit_item"]) == 'number' then
            local classId = dbase["submit_item"];
            if ItemM.getAmount(ME.user, classId) <= 0 then
                trace("BraveVSDragonM", "道具(%d)数量不足。", classId);
                return false;
            end
            ItemM.costAmount(ME.user, classId, 1);
        end
    elseif progress == 1 then
        -- 第二次触发事件
    else
        trace("BraveVSDragonM", "小男孩事件进度已完成。");
        return false;
    end

    -- 更新进度
    setLittleBoyProgess(progress + 1);

    return true;
end

-- 埋葬巨坑
function buryHugeHole(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_HUGE_HOLE then
        trace("BraveVSDragonM", "格子(%d)不是巨坑。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    -- 检查消耗
    local cost = FormulaM.invoke("CALC_HUGE_HOLE_COST");
    for _, arr in ipairs(cost) do
        local classId = arr[2];
        local amount  = arr[3];
        local curAmount = ItemM.getAmount(ME.user, classId);
        if curAmount < amount then
            trace("BraveVSDragonM", "物品(%d)数量不足，需求：%d，当前：%d", classId, amount, curAmount);
            return false;
        end
    end

    -- 扣除消耗
    for _, arr in ipairs(cost) do
        local classId = arr[2];
        local amount  = arr[3];
        ItemM.costAmount(ME.user, classId, amount);
    end

    -- 转换为坟墓
    doElementTransform(grid, pos);

    EventMgr.fire(BURY_HUGE_HOLE, { ["pos"] = pos, });

    return true;
end

-- 神官消失
function priestDisappear(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_PRIEST then
        trace("BraveVSDragonM", "格子(%d)不是神官。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    if DungeonM.isGridExist(GRID_TYPE_HUGE_HOLE) then
        trace("BraveVSDragonM", "巨坑尚未被埋葬，神官无法消失");
        return false;
    end

    -- 令格子失效
    grid:changeState(GRID_STATE_DISABLE);

    return true;
end

-- 神圣祠堂提交
function doHolyShrineSubmit(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_HOLY_SHRINE then
        trace("BraveVSDragonM", "格子(%d)不是神圣祠堂。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    -- 已经领取过建筑奖励了
    if hasGotBuildingBonus(grid.class) then
        trace("BraveVSDragonM", "已经领取过建筑奖励了。");
        return false;
    end

    local submitList = FormulaM.invoke("CALC_HOLY_SHRINE_COST");
    if type(submitList) == 'table' then
        for _, classId in ipairs(submitList) do
            if ItemM.getAmount(ME.user, classId) <= 0 then
                trace("BraveVSDragonM", "物品(%d)数量不足。", classId);
                return false;
            end
        end

        -- 扣除物品
        for _, classId in ipairs(submitList) do
            ItemM.costAmount(ME.user, classId, 1);
        end
    end

    -- 标记奖励已被领取
    setBuidingBonusTaken(grid);

    -- 执行建筑奖励
    doBuildingBonus(grid);

    return true;
end

-- 消除岩浆障碍
function removeMagaMaObstacle()
    local pos = queryMixed("magma_obstacle_pos");
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    local elementDbase = DungeonM.queryElement(grid.element, "dbase");
    if grid.type ~= GRID_TYPE_BLOCK or elementDbase["magma_obstacle"] ~= 1 then
        trace("BraveVSDragonM", "格子(%d)不是岩浆障碍。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    if hasRemovedMagaMaObstacle() then
        trace("BraveVSDragonM", "岩浆障碍已经消除。");
        return false;
    end

    -- 标记岩浆障碍已消除
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["removed_magma_obstacle"] = 1;
    ME.user.dbase:set("brave_vs_dragon", info);

    -- 解除相应建筑的封锁
    unlockBuildingByCondition("remove_magma_obstacle");

    if FeatureItemM.isFeatureOpen("awake") or 
        not AwakeM.isAwakeSystemOpen() then
        -- 令格子失效
        grid:changeState(GRID_STATE_DISABLE);
    else
        -- 转换格子
        if type(elementDbase["trans_element"]) == 'number' then
            local toElement = elementDbase["trans_element"];
            local toClass = DungeonM.queryElement(toElement, "class");
            local para = { ["class"] = toClass, ["element"] = toElement };
            DungeonM.transformGrid(toClass, pos, GRID_STATE_OPEN, para);
        end
    end

    EventMgr.fire(event.REMOVE_MAGMA_OBSTACLE, { ["pos"] = pos, });

    return true;
end

-- 获取龙王入伙选择
function getDragonKingJoinChoose()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["dragon_king_choose"];
end

-- 龙王入伙选择
function joinDragonKing(pos, choose)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_DRAGON_KING then
        trace("BraveVSDragonM", "格子(%d)不是龙王。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    if choose == CHOOSE_TYPE_REFUSE then
        -- 拒绝
        trace("BraveVSDragonM", "拒绝龙王入伙请求。");
    elseif choose == CHOOSE_TYPE_ACCEPT then
        -- 接受，执行奖励
        trace("BraveVSDragonM", "接受龙王入伙请求。");
        doBuildingBonus(grid);
    elseif choose == CHOOSE_TYPE_REFUSE_AGAIN then
        -- 再次拒绝，格子失效
        trace("BraveVSDragonM", "再次拒绝龙王入伙请求。");
        grid:changeState(GRID_STATE_DISABLE);
    else
        trace("BraveVSDragonM", "错误的选择(%d)", choose);
        return false;
    end

    -- 记录选择
    info["dragon_king_choose"] = choose;
    ME.user.dbase:set("brave_vs_dragon", info);

    return true;
end

-- 龙王城宝箱是否被开启过
function hasDragonKingBoxOpend(class)
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local openedDragonKingBox = info["opened_dragon_king_box"] or {};
    return openedDragonKingBox[class] == 1;
end

-- 打开龙王城宝箱
function openDragonKingBox(pos, optionIndex)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("BraveVSDragonM", "格子(%d)不存在。", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_DRAGON_KING_BOX then
        trace("BraveVSDragonM", "格子(%d)不是龙王城宝箱。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BraveVSDragonM", "格子(%d)已经失效。", pos);
        return false;
    end

    local class = grid.class;
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    local openedDragonKingBox = info["opened_dragon_king_box"] or {};
    if openedDragonKingBox[class] == 1 then
        trace("BraveVSDragonM", "格子(%d)的宝箱已经开启过了。", pos);
        return false;
    end

    -- 令格子失效
    grid:changeState(GRID_STATE_DISABLE);

    -- 标记宝箱已被开启
    openedDragonKingBox[class] = 1;
    info["opened_dragon_king_box"] = openedDragonKingBox;
    ME.user.dbase:set("brave_vs_dragon", info);

    -- 如果选择了打开
    local element = grid.element;
    local elementDbase = DungeonM.queryElement(element, "dbase");
    if optionIndex == 0 then
        -- 对玩家造成伤害
        local damage = elementDbase["damage"];

        -- 直接扣血
        ME.user:receiveDamage(damage, true);

        if ME.user:getHp() <= 0 then
            -- 等播放完重创效果后，再播放死亡效果
            local para = { ["delay"] = DIE_DELAY_TIME, };
            ME.user:die(para);
        end

        -- 抛出龙王城宝箱伤害事件
        EventMgr.fire(event.DRAGON_KING_BOX_DAMAGE, { ["pos"] = pos, ["damage"] = damage, });
    end

    -- 召唤出宝箱怪
    local toElement = elementDbase["trans_element"];
    DungeonM.transGridToMonster(pos, toElement, "dragon_king_box");

    return true;
end

-- 是否完成了商人任务
function hasFinishedBussiness()
    local businessManClass = 1122;
    return hasGotBuildingBonus(businessManClass);
end

-- 是保存过进度
function hasSavedProgress()
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    return info["saved_progress"] == 1;
end

-- 是否是龙王城格子
function isDragonKingCityGrid(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return false;
    end

    local elementDbase = DungeonM.queryElement(grid.element, "dbase");
    if type(elementDbase["lock_id"]) ~= 'number' then
        return false;
    end

    return queryLock(elementDbase["lock_id"], "alias") == "dragon_city";
end

-- 保存进度
function saveProgress(pos)
    if not isDragonKingCityGrid(pos) then
        trace("BraveVSDragonM", "格子(%d)不是龙王城格子。");
        return false;
    end

    if not isInBraveVSDragon() then
        trace("BraveVSDragonM", "不在勇者斗恶龙副本中。");
        return false;
    end

    if hasSavedProgress() then
        trace("BraveVSDragonM", "已经保存过进度了。");
        return false;
    end

    -- 标记进度已保存
    -- 注：这里必须先设置标记，因为验证客户端在验证时会比较两边的属性
    -- 如果服务器保存失败了，则撤销标记
    local info = ME.user.dbase:query("brave_vs_dragon") or {};
    info["saved_progress"] = 1;
    ME.user.dbase:set("brave_vs_dragon", info);

    -- 添加操作缓存
    DungeonM.addAction({ ["cmd"] = "save_brave_dragon", ["pos"] = pos, });

    -- 同步
    DungeonM.sync();

    -- 切换状态
    DungeonM.switchState(DUNGEON_GAMING);

    return true;
end

-- 读取进度
function readProgress()
    if not hasSavedProgress() then
        trace("BraveVSDragonM", "尚未保存过进度，读取失败。");
        return false;
    end

    if not isInBraveVSDragon() then
        trace("BraveVSDragonM", "不在勇者斗恶龙副本中，读取失败。");
        return false;
    end

    DungeonM.switchState(DUNGEON_LOADING);

    -- 清空同步操作缓存
    DungeonM.clearActionCache();

    local dungeonId = DungeonM.getDungeonId();
    if not DungeonServiceM.startEnterService(dungeonId) then
        trace("BraveVSDragonM", "向服务器发送请求失败。");
        DungeonM.switchState(DUNGEON_GAMING);
        return false;
    end

    trace("BraveVSDragonM", "读取进度成功，等待服务器回应。");

    return true;
end

-- 服务器保存进度的结果
function whenSaveProgress(args)
    local result = args.result;
    if result ~= 1 then
        -- 保存失败了，撤销保存标记
        trace("BraveVSDragonM", "保存进度失败，撤销保存标记。");
        local info = ME.user.dbase:query("brave_vs_dragon") or {};
        info["saved_progress"] = 0;
        ME.user.dbase:set("brave_vs_dragon", info);
    end
end

-- 能否打开门
function canOpenDoor()
    local choose = getDragonKingJoinChoose();
    if choose == CHOOSE_TYPE_ACCEPT then
        -- 已经加入了龙王
        return true;
    end

    if hasDefeatedDragon() then
        -- 已经击败了龙王
        return true;
    end

    return false;
end